package audio;

import java.awt.AWTEvent;
import java.awt.Component;
import java.awt.Toolkit;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;

import main.Contexto;

public class AudioSintetizado extends Component implements Runnable
{
	static final long serialVersionUID = 1;

	static private AudioSintetizado instancia = null;

	private ArrayList<SoundListener> listeners;

	private Socket socket;
	private PrintWriter outChannel;
	private BufferedReader inChannel;

	static final int PARADO = 0;
	static final int TOCAR = 1;
	static final int TESTAR = 2;
	static final int DESCONECTADO = 3;
	static final int DORMINDO = 4;
	protected int estado;

	private String strSaida;

	private Timer despertador;
	private EsperaFala alarmeFala;

	private AudioSintetizado()
	{
		Contexto contexto = Contexto.instancia();

		socket = null;
		inChannel = null;
		outChannel = null;
		estado = DESCONECTADO;
		strSaida = "";

		alarmeFala = null;
		despertador = new Timer();

		listeners = new ArrayList<SoundListener>();

		// Habilita o novo evento que será disparado por este objeto
		enableEvents(SoundEvent.SOUND_EVENT_ID);

		try
		{
			socket = new Socket("146.164.39.58", 1955);
			inChannel = new BufferedReader(new InputStreamReader(socket
					.getInputStream()));
			outChannel = new PrintWriter(new OutputStreamWriter(socket
					.getOutputStream()), true);

			aguardaCanal();

			if (contexto.getSistema() == Contexto.SISTEMA_WINDOWS)
			{
				outChannel.println("~R 220");
				outChannel.println("~P 110");
				outChannel.println("~S 5 5");
				outChannel.println("~U 1");
			}
			else
			{
				outChannel.println("~R 1");
				outChannel.println("~P 0");
				outChannel.println("~V 10");
			}

			estado = PARADO;
		}
		catch (UnknownHostException e)
		{
			System.err.println("localhost: host deconhecido");
			e.printStackTrace();
		}
		catch (IOException e)
		{
			System.err.println("Erro na comunicação com localhost");
			e.printStackTrace();
		}
	}

	static public AudioSintetizado instancia ()
	{
		if (instancia == null)
			instancia = new AudioSintetizado();

		return instancia;
	}

	private void aguardaCanal () throws IOException
	{
		try
		{
			while (!inChannel.ready())
			{
				// System.out.println("inchannel not ready.");
				Thread.sleep(200);
			}
		}
		catch (InterruptedException e)
		{
			e.printStackTrace();
		}
	}

	public void escreve ( String str )
	{

		if (estado != DESCONECTADO)
		{

			char ultimo;

			if (estado == DORMINDO)
			{
				System.out
						.println("Escreve chamado enquanto eu estou dormindo!");
				return;
			}

			// Elimina os brancos do início e do fim da frase.
			strSaida = str.trim();

			if (strSaida.length() > 0)
			{
				// Obtem o último caracter do cabeçalho
				ultimo = strSaida.charAt(strSaida.length() - 1);

				// Acrescenta um ponto no final da frase para melhorar a
				// prosódia.
				if (".,!?;:".indexOf(ultimo) == -1)
					strSaida += ".";
			}

			estado = TOCAR;
		}
	}

	public void abortaFala ()
	{
		if (estado != DESCONECTADO)
		{
			// Cancela o despertador
			if (estado == DORMINDO)
			{
				alarmeFala.cancel();
				alarmeFala = null;
			}

			outChannel.println("~B");
			estado = PARADO;

			System.out.println("Aborta fala");
		}
	}

	public void fechar ()
	{
		if (estado != DESCONECTADO)
		{
			// Cancela o despertador
			if (estado == DORMINDO)
			{
				alarmeFala.cancel();
				alarmeFala = null;
			}

			outChannel.println("~B");
			outChannel.println("~Q");

			estado = PARADO;

			System.out.println("Fechar");
		}
	}

	private void enviaSintetizador ()
	{
		System.out.println("Escrevi -> " + strSaida);

		if (estado != DESCONECTADO)
		{
			if (estado == DORMINDO)
			{
				System.out
						.println("Enviando ao sintetizador enquanto eu estou dormindo!");
				return;
			}

			outChannel.println(strSaida);

			System.out.println("Sleep -> " + strSaida.length() * 75);

			strSaida = "";

			// Coloca em estado dormindo e programa um despertador para o fim da
			// fala
			estado = DORMINDO;
			alarmeFala = new EsperaFala();
			despertador.schedule(alarmeFala, strSaida.length() * 75);
		}
	}

	private boolean terminouFala ()
	{
		if (estado != DESCONECTADO)
		{
			String terminou;

			try
			{
				outChannel.println("~I");

				aguardaCanal();

				terminou = inChannel.readLine();

				// Ignorar mensagem de cabeçalho do servidor
				while (terminou.charAt(0) == '+')
					terminou = inChannel.readLine();

				return terminou.equals("0");
			}
			catch (IOException e)
			{
				e.printStackTrace();
			}
		}

		return true;
	}

	public void run ()
	{
		boolean running;

		running = true;
		while (running)
		{
			switch (estado)
			{
			case TOCAR:
				enviaSintetizador();
				break;
			case TESTAR:
				if (terminouFala())
				{
					estado = PARADO;
					mandaEvento();
				}
				break;
			}

			/*
			 * Não tem nada para fazer. Dorme um pouquinho (200 ms) try {
			 * Thread.sleep(200); } catch (InterruptedException ie) {
			 * ie.printStackTrace(); }
			 */

		}
	}

	// Registra novo ouvinte para o evento SoundEvent
	public void addSoundListener ( SoundListener listener )
	{
		listeners.add(listener);
	}

	// Cancela registro de ouvinte para o evento SoundEvent
	public void removeSoundListener ( SoundListener listener )
	{
		while (listeners.remove(listener))
			;
	}

	// Obtem todos os ouvintes para o evento SoundEvent
	public SoundListener[] getSoundListeners ()
	{
		return listeners.toArray(new SoundListener[listeners.size()]);
	}

	/*
	 * Insere um evento SoundEvent na fila de eventos do sistema Existe uma
	 * thread só para atender essa fila Ela executa o método processEvent da
	 * "fonte" do evento Fonte do evento = objeto que executou o método
	 * fireSoundEvent
	 */
	private void mandaEvento ()
	{
		Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(
				new SoundEvent(this));
	}

	/*
	 * Processa o evento SoundEvent Iteradores permitem caminhar em uma lista
	 * "it" é do tipo ListIterator<SoundListener> it.next() retorna um
	 * SoundListener O método progressOccurred é definido na interface
	 * SoundListener
	 */
	public void processEvent ( AWTEvent e )
	{
		for (SoundListener listener : listeners)
		{
			listener.FimDeFala((SoundEvent) e);
		}
	}

	private class EsperaFala extends TimerTask
	{
		@Override
		public void run ()
		{
			estado = TESTAR;
		}

	}

}
